﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

/*
 * Representa objetos relocaveis
 * 
 */

namespace J_Objects
{
    public class ObjRelo
    {
        // Criação e persistência
        //
        public ObjRelo()
        {
            // simples
        }
        public ObjRelo(BinaryReader brObj)
        {
            // Criar objeto relocavel do arquivo

            // testes de integridade
            //
            if (brObj.BaseStream.Length < 20)
                // 20 bytes = Formula mágica + VersaoFormato + IdProc
                throw new ApplicationException(
                    String.Format("Tamanho do arquivo, {0} bytes, é insuficiente.", brObj.BaseStream.Length));

            byte[] fm = brObj.ReadBytes(8);
            for (int ii = 0; ii < 8; ii++)
                if (fm[ii] != FormulaMagica[ii])
                    throw new ApplicationException(
                        String.Format("Formula mágica {0} invalida.", Convert.ToString(fm)));

            UInt32 vv = brObj.ReadUInt32();
            if (vv != VersaoFormato)
                throw new ApplicationException(
                    String.Format("Versão do formato, {0} bytes, é incompativel.", vv));

            // Leitura de objetos de tamanho fixo
            //
            // ...gerenciamento de programa
            IdProc = brObj.ReadBytes(8);
            Absoluto = brObj.ReadBoolean();
            TemStart = brObj.ReadBoolean();
            Relocavel = brObj.ReadBoolean();
            dummy4 = brObj.ReadByte();
            edcStart = brObj.ReadUInt64();
            idxNomePrograma = brObj.ReadUInt32();
            idxNomeArquivo = brObj.ReadUInt32();

            // ...gerenciar a imágem binária
            edcPrimeiro = brObj.ReadUInt64();
            TamBin = brObj.ReadUInt64();

            // ...gerenciar os demais coleções de conteúdo variável
            NumFixup = brObj.ReadUInt32();
            NumCompat = brObj.ReadUInt32();
            NumSimbo = brObj.ReadUInt32();
            NumString = brObj.ReadUInt32();


            // Leitura de objetos de tamanho variavel. Vou realizar Compats mais tarde
            //
            for (int ii = 0; ii < NumFixup; ii++) Fixups[ii] = new FixupRelo(brObj);
            //for (int ii = 0; ii < NumCompat; ii++) Compats[ii] = new >>>Compat<<<(brObj);
            for (int ii = 0; ii < NumSimbo; ii++) Simbolos[ii] = new SimboRelo(brObj);
            for (int ii = 0; ii < NumString; ii++) Strings[ii] = brObj.ReadString();
        }
        public void Gravar(BinaryWriter bwObj)
        {
            // Gravar o objeto para um arquivo

            // o cabecçalho
            bwObj.Write(FormulaMagica);
            bwObj.Write(VersaoFormato);
            bwObj.Write(IdProc);

            bwObj.Write(Absoluto);
            bwObj.Write(TemStart);
            bwObj.Write(Relocavel);  
            bwObj.Write(dummy4);
            bwObj.Write(edcStart);
            bwObj.Write(idxNomePrograma);  // 1-baseado index do string que é o nome; ou 0
            bwObj.Write(idxNomeArquivo);  // 1-baseado index do string que é o nome; ou 0
            bwObj.Write(edcPrimeiro);
            bwObj.Write(TamBin);

            bwObj.Write(NumFixup);
            bwObj.Write(NumCompat);
            bwObj.Write(NumSimbo);
            bwObj.Write(NumString);

            // as subunidades
            if (Binario != null) bwObj.Write(Binario);
            foreach (FixupRelo ff in Fixups) ff.GravArquivo(bwObj);
            // compats
            foreach (SimboRelo si in Simbolos) si.GravArquivo(bwObj);
            foreach (String ss in Strings) bwObj.Write(ss);
        }

        // Campos do objeto
        //

        // gerenciamento do objeto
        byte[] FormulaMagica = new byte[]{
            (byte)'J',
            (byte)'5',
            (byte)'O',
            (byte)'b',
            (byte)'j',
            (byte)'e',
            (byte)'c',
            (byte)'t'};  // ajuda evitar ler arquivos incompativeis
        const uint VersaoFormato = 1;

        // gerenciamento de programa
        public byte[] IdProc;  // 8 byte identidade de processador
        public bool Absoluto = false;
        public bool TemStart = false;
        public bool Relocavel = true;  // diferenciar entre objetos executaveis e relocaveis
        public byte dummy4 = 0;  // alinhamento 4 bytes
        public UInt64 edcStart;
        public UInt32 idxNomePrograma;  // 1-baseado index do string que é o nome; ou 0
        public UInt32 idxNomeArquivo;  // 1-baseado index do string que é o nome; ou 0

        // gerenciar a imágem binária
        public UInt64 edcPrimeiro;
        public UInt64 TamBin;

        // gerenciar os demais coleções de conteúdo variável
        public uint NumFixup;
        public uint NumCompat;
        public uint NumSimbo;
        public uint NumString;

        // objetos variaveis
        public byte[] Binario;
        public FixupRelo[] Fixups;
        public byte[][] Compats;
        public SimboRelo[] Simbolos;
        public String[] Strings;

        // Métodos para manipular valores dentro do objeto
        // utilizados principalmente em contexto de fixups
        //
        // ############ somente para little-endian. deve ser generalizado
        //
        public static UInt64 GetEdc(byte[] Binario, UInt64 Endereco, uint Tamanho)
        {
            // extrair um valor de <Tamanho> bytes da imagem binária

            if ((Tamanho == 0) | (Tamanho > 8)) throw new ApplicationException(String.Format("Tamanho {0} ilegítimo", Tamanho));
            if (Endereco + (UInt64)Tamanho > (UInt64)Binario.Length - 1) throw new ApplicationException(String.Format("Tamanho {0} da imagem binária é insuficiente", Tamanho));

            // extrair os bytes
            byte[] bb = new byte[Tamanho];
            for (int ii = 0; ii < Tamanho; ii++) bb[ii] = Binario[Endereco + (ulong)ii];

            // adaptar endianness.. if (BitConverter.IsLittleEndian != PROC.IsLittleEndian) Array.Reverse(bb)

            // transformar
            return BitConverter.ToUInt64(bb, 0);
        }
        public static void SetEdc(ref byte[] Binario, UInt64 Valor, UInt64 Endereco, uint Tamanho)
        {
            // inserir um valor em uma imagem binária

            // teste de tamanho/valor legítimos
            if ((Tamanho == 0) | (Tamanho > 8)) throw new ApplicationException(String.Format("Tamanho {0} do valor InvalidOperationException.", Tamanho));
            if (Endereco + (UInt64)Tamanho > (UInt64)Binario.Length - 1) throw new ApplicationException(String.Format("Tamanho {0} da imagem binária é insuficiente", Tamanho));
            if (Tamanho < 8)  // tamanho == 8 aceita todos os UInt64s
                if (Valor >= (UInt64)(1L << (8 * (int)Tamanho))) throw new ApplicationException(String.Format("Valor {0} não cabe em tamanho de {1} bytes.", Valor, Tamanho));

            // produzir os bytes
            byte[] bb = BitConverter.GetBytes(Valor);  // GetBytes returns byte[8]

            // adaptar endianness.. if (BitConverter.IsLittleEndian != PROC.IsLittleEndian) Array.Reverse(bb)

            // inserir ao objeto alvo
            for (int ii = 0; ii < Tamanho; ii++) Binario[Endereco + (UInt64)ii] = bb[ii];
        }
        public static void AddEdc(ref byte[] Binario, Int64 Valor, UInt64 Endereco, uint Tamanho)
        {
            // adicionar um valor de deslizamento positivo ou negativo para o endereço dentro da imagem binária
            UInt64 Original = GetEdc(Binario, Endereco, Tamanho);
            UInt64 Novo;
            try { checked { Novo = (UInt64)((Int64)Original + Valor); } }
            catch (OverflowException oe) { throw new ApplicationException(oe.Message); }
            SetEdc(ref Binario, Novo, Endereco, Tamanho);
        }
    }
}
